package top.yangbuyi.system.controller;


import com.wf.captcha.ArithmeticCaptcha;
import com.wf.captcha.ChineseGifCaptcha;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import top.yangbuyi.system.common.*;
import top.yangbuyi.system.config.annotation.TimeAno;
import top.yangbuyi.system.domain.Loginfo;
import top.yangbuyi.system.domain.Menu;
import top.yangbuyi.system.domain.User;
import top.yangbuyi.system.service.LoginfoService;
import top.yangbuyi.system.service.MenuService;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * ClassName: LoginController
 * Description: 杨不易网站 :www.yangbuyi.top
 * date: 2020/4/14 20:01
 *
 * @author TeouBle
 * @author yangbuyi
 * @since JDK 1.8
 */
@Controller
@Slf4j
@RequestMapping("api/login")
public class LoginController {
      
      @Autowired
      private MenuService menuService;
      @Autowired
      private LoginfoService loginfoService;
      
      @Autowired
      private StringRedisTemplate redisTemplate;
      
      @Autowired
      private SessionDAO sessionDAO;
      
      /**
       * 用户登陆
       *
       * @param loginname 传递来的登陆名称
       * @param password  传递来打密码
       * @param keyCode   传递来的验证码名称 key
       * @param captcha   传递来的验证码
       * @return 返回登陆信息
       */
      @RequestMapping("doLogin")
      @ResponseBody
      public ResultObj doLogin(String loginname, String password, String keyCode, String captcha, HttpServletResponse response) {
            // 获取redis验证码
            ValueOperations<String, String> forValue = redisTemplate.opsForValue();
            String code = forValue.get(keyCode);
            if (null == code) {
                  return new ResultObj(-1, "当前验证码过期请重新获取");
            } else {
                  try {
                        System.out.println(captcha);
                        // 进行比较 不考虑大小写
                        if (code.equalsIgnoreCase(captcha)) {
                              
                              
                              // 获取主体
                              Subject subject = SecurityUtils.getSubject();
                              // 认证
                              UsernamePasswordToken passwordToken = new UsernamePasswordToken(loginname, password);
                              subject.login(passwordToken);
                              // ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
                              // 通过Shiro获取 Token   sessionid = token
                              String token = subject.getSession().getId().toString();
                              /* 写入登陆日志开始 */
                              Loginfo loginfo = new Loginfo();
                              String remoteAddr = WebUtils.getHttpServletRequest().getHeader("X-Forwarded-For");
                              ActiveUser active = (ActiveUser) subject.getPrincipal();
                              loginfo.setLoginip(remoteAddr);
                              loginfo.setLoginname(active.getUser().getName() + "-" + active.getUser().getLoginname());
                              loginfo.setLogintime(new Date());
                              loginfoService.save(loginfo);
                              /* 写入登陆日志结束 */
                              
                              
                              // 存储 前端登陆需要的信息
                              Map<String, Object> map = new HashMap<>();
                              map.put("token", token);
                              map.put("permissions", active.getPermissions());
                              map.put("username", active.getUser().getName());
                              map.put("usertype", active.getUser().getType());
                              System.err.println("TOKEN:" + token);
                              
                              
                              // 判断是否 不可用/
                              if (active.getUser().getAvailable().equals(Constant.AVAILABLE_FALSE)) {
                                    return new ResultObj(-1, "您已被禁止登陆，请联系管理员1692700664");
                              } else {
                                    // 带入参数 冲冲冲
                                    log.info("登陆成功");
                                    
                                    /* 单用户登陆 */
//                                    singleUserLogin(token, loginname);
                                    
                                    return new ResultObj(200, "登陆成功", map);
                              }
                        } else {
                              redisTemplate.delete(keyCode);
                              log.warn("验证码错误");
                              return new ResultObj(-1, "验证码错误");
                        }
                  } catch (AuthenticationException e) {
                        redisTemplate.delete(keyCode);
                        log.warn("用户名或密码不正确");
                        return new ResultObj(-1, "用户名或密码不正确");
                  } catch (Exception e) {
                        e.printStackTrace();
                        log.debug("未知错误，请联系管理员1692700664");
                        return new ResultObj(-1, "未知错误，请联系管理员1692700664");
                  }
                  
                  
            }
      }
      
      
      // 获取session
      protected String doReadSession(Serializable sessionId) {
            log.debug("获取session:{}", sessionId);
            // 先从缓存中获取session，如果没有再去数据库中获取
            String s = redisTemplate.opsForValue().get("shiro:session:" + sessionId.toString());
            return s;
      }
      
      
      /**
       * @Description: 杨不易个人网址:http://yangbuyi.top
       * @Param: 单用户登陆  有BUG
       * @return:
       * @Author: Mr.yang
       * @Date: 2020/7/11
       */
      public void singleUserLogin(String token, String loginname) {
            HttpSession session = WebUtils.getHttpServletRequest().getSession();
            String id = session.getId();
            log.info("session的ID:{}" + id + "{}Shiro的ID:" + token);
            String s = redisTemplate.opsForValue().get(loginname);
            
            if (s != null) {
                  // 表示 有该用户 我们进行判断 是否为当前用户
                  
                  // if (s.equals(id)) { // 一致的
                  
                  String shiro_session = redisTemplate.opsForValue().get("shiro:session:" + token);
                  
                  if (shiro_session != null) {
                        redisTemplate.delete("shiro:session:" + s);
                        // redisTemplate.delete(loginname);
                        System.err.println(s + ":" + id);
                        System.err.println("session的ID:{}" + id + "{}Shiro的ID:" + token);
                        log.info("你的帐号已在别处登陆，挤掉他");
                        
                  }
                  
                  //  }
            } else {
                  redisTemplate.opsForValue().set(loginname, token, 3000, TimeUnit.SECONDS);
            }
      }
      
      
      /**
       * 获取用户信息
       *
       * @return 用户信息
       */
      @GetMapping("getInfo")
      @ResponseBody
      public AjaxResult getInfo() {
            System.out.println("getInfo");
            // 获取主体
            Subject subject = SecurityUtils.getSubject();
            ActiveUser principal = (ActiveUser) subject.getPrincipal();
            AjaxResult ajax = AjaxResult.success();
            ajax.put("user", principal.getUser());
            ajax.put("roles", principal.getRoles());
            ajax.put("permissions", principal.getPermissions());
            return ajax;
      }
      
      /**
       * 初始化
       * 加载所有菜单[顶部菜单和左侧菜单]
       *
       * @return
       */
      @TimeAno
      @RequestMapping("loadIndexMenu")
      @ResponseBody
      public Object loadIndexMenu() {
            // 1.思路: 需要判断当前是否为超级管理员  返回不同的菜单
            // 获取当前用户
            Subject subject = SecurityUtils.getSubject();
            ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
            User user = activeUser.getUser();
            if (null != user) {
                  // 存储菜单
                  List<Menu> menus = null;
                  if (user.getType().equals(Constant.USER_TYPE_SUPER)) {
                        // 超级管理员
                        // 进行查询
                        menus = menuService.queryAllMenuForList();
                  } else {
                        // 进行查询
                        // 根据当前用户id 查询 当前用拥有的菜单
                        menus = menuService.queryMenuForListByUserId(user.getId());
                  }
                  // 组装树
                  List<MenuTreeNode> treeNodes = new ArrayList<>();
                  for (Menu m : menus) {
                        // 判断一下是否展开  前端需要的为Boolean类型而数据储存的为 int类型
                        Boolean spread = m.getSpread() == Constant.SPREAD_TRUE ? true : false;
                        // 每一次添加菜单
                        treeNodes.add(new MenuTreeNode(m.getId(), m.getPid(), m.getTitle(), m.getHref(), m.getIcon(), spread, m.getTarget(), m.getTypecode()));
                  }
                  // 组装树  进行组装子节点
                  List<MenuTreeNode> build = MenuTreeNode.MenuTreeNodeBuild.build(treeNodes, 0);
                  // 使用链式进行排序让后添加的菜单 放在后面去 固定系统和业务菜单前置
                  LinkedHashMap<String, Object> map = new LinkedHashMap<>();
                  // 最后组装json类型
                  for (MenuTreeNode node : build) {
                        /*
                         * 组装类型
                         * {
                         * 	"business":{   },
                         * 	"system":{   }
                         * }
                         * */
                        map.put(node.getTypecode(), node);
                  }
                  return map;
            }
            return null;
      }
      
      
      /**
       * 验证是否登陆过
       *
       * @return
       */
      @RequestMapping("checkLogin")
      @ResponseBody
      public ResultObj checkLogin() {
            Subject subject = SecurityUtils.getSubject();
            // 判断是否认证成功
            System.out.println("登陆认证：" + subject.isAuthenticated());
            if (subject.isAuthenticated()) {
                  
                  // 进行判断二次登录
                  
                  
                  return ResultObj.IS_LOGIN;
            } else {
                  return ResultObj.UN_LOGIN;
            }
      }
      
      /**
       * 返回验证码
       * 思路： 把验证码存入redis
       * 登陆时候 在进行获取出来 进行相应的判断
       * 接收的形式    key --- value
       *
       * @param response 返回出去的流
       * @param codeKey  接收验证码的key
       * @throws IOException
       */
      @RequestMapping("captcha")
      public void captcha(HttpServletResponse response, String codeKey) throws IOException, FontFormatException {
          /*  // 定义验证码
            ShearCaptcha captcha = CaptchaUtil.
                  createShearCaptcha(100, 38, 4, 2);
            String code = captcha.getCode();
            System.out.println(code);
            
            // 储存到redis
            ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
            opsForValue.set(codeKey, code);
            // 设置  缓存时间 60秒
            opsForValue.getOperations().expire(codeKey, 60, TimeUnit.SECONDS);
            // 返回流
            captcha.write(response.getOutputStream());*/
            // 定义随机数进行  判断用哪一个验证码
            int random = (int) (Math.random() * 3);
            if (random == 0) {
                  // 算数验证码
                  ArithmeticCaptcha captcha = new ArithmeticCaptcha(130, 48);
                  captcha.setLen(3);  // 几位数运算，默认是两位
                  captcha.getArithmeticString();  // 获取运算的公式：3+2=?
                  captcha.text();  // 获取运算的结果：5
                  
                  String verCode = captcha.text().toLowerCase();
                  System.out.println("算数验证码: " + verCode);
                  // 存入redis并设置过期时间为30分钟
                  ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
                  opsForValue.set(codeKey, verCode);
                  // 设置  缓存时间 60秒
                  opsForValue.getOperations().expire(codeKey, 60, TimeUnit.SECONDS);
                  // 将key和base64返回给前端
                  captcha.out(response.getOutputStream());
                  
            } else if (random == 1) {
                  // 字母验证码 美化
                  SpecCaptcha captcha = new SpecCaptcha(130, 48, 4);
                  captcha.setCharType(Captcha.TYPE_ONLY_UPPER);
                  String verCode = captcha.text().toLowerCase();
                  System.out.println("字母验证码: " + verCode);
                  // 存入redis并设置过期时间为30分钟
                  ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
                  opsForValue.set(codeKey, verCode);
                  // 设置  缓存时间 60秒
                  opsForValue.getOperations().expire(codeKey, 60, TimeUnit.SECONDS);
                  // 将key和base64返回给前端
                  captcha.out(response.getOutputStream());
            } else {
                  // 中文gif类型
                  ChineseGifCaptcha captcha = new ChineseGifCaptcha(130, 48);
                  
                  String verCode = captcha.text().toLowerCase();
                  System.out.println("中文gif类型: " + verCode);
                  // 存入redis并设置过期时间为30分钟
                  ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
                  opsForValue.set(codeKey, verCode);
                  // 设置  缓存时间 60秒
                  opsForValue.getOperations().expire(codeKey, 60, TimeUnit.SECONDS);
                  // 将key和base64返回给前端
                  captcha.out(response.getOutputStream());
            }
//            return JsonResult.ok().put("key", key).put("image", specCaptcha.toBase64());
      }
      
      
      /**
       * 退出登陆
       *
       * @param request
       * @return
       */
      @RequestMapping("logout")
      @ResponseBody
      public ResultObj logout(HttpServletRequest request) {
            
            // 清楚session
            HttpSession session = request.getSession();
            session.invalidate();
            
            // 退出认证
            Subject subject = SecurityUtils.getSubject();
            subject.logout();
            
            // 清理缓存
            String header = org.apache.shiro.web.util.WebUtils.toHttp(request).getHeader("TOKEN");
            // 删除redis缓存当中的  token
            redisTemplate.delete("shiro:session:" + header);
            
            return new ResultObj(200, "success");
            
      }
      
      
}